module Fileutils where
--TODO TODO There are lot of functions doing string manipulation. Put them in a separate
-- file and write unit tests
import System.IO
import Data.Maybe
import Control.Exception (handle)
import Control.Monad
import Data.List

data Distro = Redhat |
		Debian |
		Suse |
		Linux26
		deriving (Eq,Show)
data VncServer = VncServer {userName::String, 
		  port :: String,
		  configString :: String
		} deriving (Eq,Show)

getVncServers :: IO [VncServer]
getVncServers = do
    wrapper <- getDistro 
    case wrapper of
        Just distro -> do
            let configFileName = getVncConfigFile (fromJust wrapper)
            inhWrapper <- safeOpenFile configFileName ReadMode
            case inhWrapper of
                Just inh -> do
                    fileContents <- hGetContents inh
                    let vncData = populateVncData $ lines fileContents
--                  return []
                    case vncData of
                        Just d -> return (populateVncServers d)
                        Nothing -> return []
                Nothing -> return []
        Nothing -> return []

getUser :: VncServer -> String
getUser vncserver = userName vncserver

getPort :: VncServer -> Integer
getPort vncserver = (read.port) vncserver

getConfigString :: [VncServer] -> String-> String
getConfigString vncservers key=foldl (\str (VncServer userName port configString)-> if key == (port++userName) then (str++configString) else (str++[])) [] vncservers
            
myDistrohandler _ = return (Just Linux26)

myFileOpenHandler _ = return Nothing

getDistro :: IO (Maybe Distro)
getDistro = handle myDistrohandler $do
		releaseHandle <- openFile "/etc/redhat-release" ReadMode
		return (Just Redhat)

populateVncServers :: [(String, String)]->[VncServer]
populateVncServers vncData = map (createVncServer) vncData

-- (a,b) looks like this ("1:narasim", "-geometry 800x600")
createVncServer :: (String, String) -> VncServer
createVncServer (a,b) = let maybeIndex = findIndex (\ch -> ch == ':') a
                        in
                            case maybeIndex of 
                                Just index -> VncServer (snd (splitAt index a)) (fst (splitAt index a)) b
                                Nothing -> undefined

-- We should check for file existence here and the 
-- interface will change to IO FileName
getVncConfigFile :: Distro -> String
getVncConfigFile Redhat = "/etc/sysconfig/vncservers"
getVncConfigFile _ = "" 

-- Return Nothing if the String is in incorrect format
-- Parse VNC configuration
populateVncData :: [String] -> Maybe [(String, String)]
populateVncData fileLines = 
    let pairs = getPairs fileLines
    in
        if pairs == [] 
            then Nothing
            else (Just pairs)

--TODO Glaring mistake VNCSERVERS=" ", the quotes have not been taken care of
-- Index matching will fail
getPairs fileLines= 
    let usefulLines = getUsefulLines (fileLines)
	-- From seeing how vncserver works, it does not read more than
	-- one line of VNCSERVERS from the config file, or atleast it 
	-- only takes into account the last line
        serverList = getServerStrings usefulLines
        serverargsList = getServerArgsStrings usefulLines
    in
         [(x,y) | x<-serverList, y<-serverargsList, ((x!!0) == (y!!1))]

safeOpenFile :: String ->  IOMode -> IO (Maybe Handle)
safeOpenFile path ReadMode = handle myFileOpenHandler $ do
	inh <- openFile path ReadMode
	return (Just inh)

checkComment :: String -> Bool
checkComment [] = False
checkComment (x:xs) = (x == '#')

checkEmptyLine :: String -> Bool
checkEmptyLine [] = True
checkEmptyLine _ = False

getUsefulLines :: [String] -> [String]
getUsefulLines str = filter (\x->(not $ checkComment x) && (not $ checkEmptyLine x)) str

isServerLine :: String -> Bool
isServerLine str = startsWith "VNCSERVERS=" str

isServerArgsLine :: String -> Bool
isServerArgsLine str = startsWith "VNCSERVERARGS" str

startsWith :: String -> String -> Bool
startsWith [] _ = False
startsWith _ [] = False
startsWith substr str = (take (length substr) str) == substr

chopServerLine :: String->[String]
chopServerLine [] = []
chopServerLine serverLine =  let server =  takeWhile (isNotWhiteSpace) (serverLine)
                                 restOfLine = dropWhile (isNotWhiteSpace) serverLine
                                 nextLine = dropWhile (isWhiteSpace) restOfLine
                            in
				if (server == "")
				then chopServerLine nextLine
				else  server:(chopServerLine nextLine)

isWhiteSpace :: Char -> Bool
isWhiteSpace a = (a == '\t') || (a == ' ')

isNotWhiteSpace :: Char -> Bool
isNotWhiteSpace = not.isWhiteSpace

getServerStrings :: [String] -> [String]
getServerStrings lines = 
    let (line:_) = filter (isServerLine) lines
    -- TODO highly dense, what we want to do is to drop " "at the beginning and end of string
        actualLine = reverse (drop 1 (reverse (drop (length "VNCSERVERS=\"") line)))
    in
        chopServerLine actualLine

getServerArgsStrings :: [String]->[String]
getServerArgsStrings lines = map (drop (length "VNCSERVERARGS")) (filter (isServerArgsLine) lines)
